import 'dart:io';

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:video_player/video_player.dart';
import 'package:audioplayers/audioplayers.dart';

import 'package:zz_video_maker/zz_video_maker.dart';

import '../edited_data_controller.dart';
import 'package:path/path.dart' as p;

class VideoEditorViewModel extends EditedDataController {
  final VMSDKWidget vmsdkWidget = VMSDKWidget();

  var initializedData = false;
  var aspectRatio = ERatio.ratio11.obs;
  var currentIndex = 0.obs;
  int? lastIndex;
  final Map<EditedMedia, VideoPlayerController> _controllers = {};

  final Map<VideoPlayerController, VoidCallback> _listeners = {};

  var progress = 0.0.obs;
  var generateStatus = EGenerateStatus.none.obs;
  var position = 0.obs;
  final player = AudioPlayer();

  VideoEditorViewModel({required super.isAsset, required super.path});

  void setPosition(int value) {
    var allEditedData = this.allEditedData;
    if (allEditedData == null) {
      return;
    }

    var list = allEditedData.startTimes;
    for (var i = list.length - 1; i >= 0; i--) {
      var e = list[i];
      if (value >= (e * 1000).toInt()) {
        if (currentIndex.value != i) {
          currentIndex.value = i;
        }
        var data = allEditedData.editedMediaList[i];
        controller(i).seekTo(Duration(
            milliseconds:
                (data.startTime * 1000).toInt() + value - (e * 1000).toInt()));

        break;
      }
    }
    position.value = value;
    player.seek(Duration(milliseconds: value));
  }

  void setCurrentIndex(int index) {
    var allEditedData = this.allEditedData;
    if (allEditedData == null) {
      return;
    }
    currentIndex.value = index;
    position.value = (allEditedData.startTimes[index] * 1000).toInt();
    player.seek(Duration(milliseconds: position.value));
    player.pause();
  }

  bool isTrimmerEditing = false;

  EditedMedia? get currentEditedMedia =>
      allEditedData?.editedMediaList[currentIndex.value];

  @override
  Future<void> onInit() async {
    if (!vmsdkWidget.isInitialized) {
      await vmsdkWidget.initialize();
    }
    super.onInit();
    ever(aspectRatio, (callback) {
      allEditedData?.resolution = Resolution.fromRatio(aspectRatio.value);
      allEditedData?.ratio = aspectRatio.value;
      update(['playerView']);
    });
    ever(currentIndex, (callback) {
      if (lastIndex != null && lastIndex != currentIndex.value) {
        removeController(lastIndex!);
      }
      lastIndex = currentIndex.value;
    });
  }

  @override
  void onClose() {
    super.onClose();
    removeAllController();
  }

  void removeAllController() {
    for (var i = 0; i < _controllers.values.length; i++) {
      var e = _controllers.values.elementAt(i);
      if (_listeners[e] != null) {
        e.removeListener(_listeners[e]!);
        e.pause();
        e.dispose();
      }
    }

    _controllers.clear();
    _listeners.clear();
  }

  VoidCallback listenerSpawner(int index) {
    if (checkIndexError(index)) {
      throw Exception('数组越界');
    }
    EditedMedia editedMedia = allEditedData!.editedMediaList[index];

    return () {
      var con = controller(index);
      int start = (editedMedia.startTime * 1000).toInt();
      // int end = ((editedMedia.duration + editedMedia.startTime) * 1000).toInt();
      int duration = (editedMedia.duration * 1000).toInt();

      int pos = con.value.position.inMilliseconds - start;
      int dur = duration;

      position.value = pos + (allEditedData!.startTimes[index] * 1000).toInt();
      print(
          'index $index - pos $pos - start $start - dur $dur - position ${position.value}');
      if (pos > dur - 1) {
        if (index < allEditedData!.editedMediaList.length - 1) {
          playController(currentIndex.value + 1, isAuto: true);
        } else {
          pause();
        }
      } else if (pos < 0) {
        con.seekTo(Duration(milliseconds: start));
      }
    };
  }

  void playController(int index, {bool isAuto = false}) async {
    if (checkIndexError(index)) {
      return;
    }
    currentIndex.value = index;

    EditedMedia editedMedia = allEditedData!.editedMediaList[index];
    var con = controller(index);
    if (!_listeners.keys.contains(con)) {
      var spawner = listenerSpawner(index);
      con.addListener(spawner);
      _listeners[con] = spawner;
    }

    initController(index - 1);
    initController(index + 1);

    con.seekTo(Duration(milliseconds: (editedMedia.startTime * 1000).toInt()));
    await con.play();
    if (!isAuto) {
      player.seek(Duration(
          milliseconds: (allEditedData!.startTimes[index] * 1000).toInt()));
    }

    position.value = (allEditedData!.startTimes[index] * 1000).toInt();
  }

  EditedMedia currentEditorData() {
    return allEditedData!.editedMediaList[currentIndex.value];
  }

  VideoPlayerController currentController() {
    return controller(currentIndex.value);
  }

  VideoPlayerController controller(int index) {
    if (checkIndexError(index)) {
      throw Exception('error');
    }
    var res = _controllers[allEditedData!.editedMediaList.elementAt(index)];
    if (res == null) {
      initController(index);
      res = _controllers[allEditedData!.editedMediaList.elementAt(index)];
    }
    return res!;
  }

  Future<void> initController(int index, {bool isUpdatePlayer = false}) async {
    if (checkIndexError(index)) {
      return;
    }

    var data = allEditedData!.editedMediaList.elementAt(index);
    var oldCon = _controllers[data];
    if (oldCon != null) {
      removeController(index);
    }

    VideoPlayerOptions options = VideoPlayerOptions();
    VideoPlayerController controller = VideoPlayerController.file(
        File(data.mediaData.imageVideoPath ?? data.mediaData.absolutePath),
        videoPlayerOptions: options);

    _controllers[data] = controller;

    await controller.initialize().then((value) {
      if (isUpdatePlayer) {
        updatePlayer();
        controller
            .seekTo(Duration(milliseconds: (data.startTime * 1000).toInt()));
      }
    });
  }

  void removeController(int index) {
    if (checkIndexError(index)) {
      return;
    }
    EditedMedia editedMedia = allEditedData!.editedMediaList[index];

    var con = controller(index);
    // con.pause();
    Future.delayed(const Duration(milliseconds: 500), () {
      con.dispose();
    });
    _controllers.remove(editedMedia);
    var res = _listeners.remove(con);
    if (res != null) {
      con.removeListener(res);
    }
  }

  bool get isPlaying => controller(currentIndex.value).value.isPlaying;

  void pause() {
    controller(currentIndex.value).pause();
    player.pause();
  }

  void play() {
    var con = controller(currentIndex.value);
    if (!_listeners.keys.contains(con)) {
      var spawner = listenerSpawner(currentIndex.value);
      con.addListener(spawner);
      _listeners[con] = spawner;
    }
    con.play();
    lastIndex = currentIndex.value;
    player.resume();
  }

  initialize() async {
    removeAllController();
    var allEditedData = this.allEditedData;
    if (allEditedData == null) {
      return;
    }

    if (allEditedData.editedMediaList.isNotEmpty) {
      await initController(0, isUpdatePlayer: false);
    }

    if (allEditedData.editedMediaList.length > 1) {
      await initController(1);
    }
  }

  bool checkIndexError(index) {
    var allEditedData = this.allEditedData;
    if (allEditedData == null) {
      return true;
    }

    if (index < 0 || index > allEditedData.editedMediaList.length - 1) {
      return true;
    }
    return false;
  }

  void updateAll() {
    updatePlayer();
    updateSticker();
    updateText();
    updateFrame();
    updatePlayerView();
  }

  void updateFrame() {
    update(['frame']);
  }

  void updateSticker() {
    update(['sticker']);
  }

  void updateText() {
    update(['text']);
  }

  void updateTimer() {
    update(['timer']);
  }

  void updateTrimmer(bool isTrimmerEditing, {bool isUpdatePlayer = false}) {
    this.isTrimmerEditing = isTrimmerEditing;
    update(['trimmer']);
    if (isUpdatePlayer) {
      update(['timer']);
      updatePlayerView();
    }
  }

  void updatePlayerView() {
    update(['playerView']);
  }

  void updatePlayer() {
    if (checkIndexError(currentIndex.value)) {
      return;
    }
    EditedMedia editedMedia =
        allEditedData!.editedMediaList[currentIndex.value];
    initController(currentIndex.value).then((value) {
      controller(currentIndex.value).seekTo(
          Duration(milliseconds: (editedMedia.startTime * 1000).toInt()));
      update(['player']);
    });
  }

  @override
  dynamic initializeDataComplete() async {
    // initializedData = false;
    // try {
    //   Get.find<VideoPlayerViewModel>().removeAllListener();
    // } catch (e) {
    //   print(e);
    // }
    // update([EditedDataController.initializedDataId]);
    var allEditedData = this.allEditedData;
    if (allEditedData != null) {
      if (currentIndex.value >= allEditedData.editedMediaList.length) {
        currentIndex.value -= 1;
        allEditedData.duration;
        setPosition(
            (allEditedData.startTimes[currentIndex.value] * 1000).toInt());
      }

      for (var element in allEditedData.editedMediaList) {
        await element.handle();

        for (var textData in element.editedTexts) {
          try {
            await vmsdkWidget.extractPreview(textData).then((value) {
              textData.previewImagePath = value.$1;
              textData.width = value.$2?.width.toInt() ?? 0;
              textData.height = value.$2?.height.toInt() ?? 0;
            });
          } catch (e) {}
        }
      }

      await initialize();
      await ResourceManager.getInstance().loadResourceFromAssets(
          allEditedData.editedMediaList, allEditedData.ratio);
      aspectRatio.value = allEditedData.ratio;

      if (allEditedData.musicList.isNotEmpty) {
        for (var element in allEditedData.musicList) {
          final String filename = p.basename(element.absolutePath!);

          final writeFile =
              await copyAssetToLocalDirectory("raw/audio/$filename");
          element.absolutePath = writeFile.path;
          await player.play(
              DeviceFileSource(allEditedData.musicList.first.absolutePath!));
          player.pause();
          break;
        }
      }

      initializedData = true;
      update([EditedDataController.initializedDataId]);
    }
  }
}
